package com.jianganming.proxy.myproxy;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * 手写的代理类
 */
public class MyProxy {
    private static final String lin = "\r\n";
    private static final String tt = "\t";

    private static void generateProxy(Class[] interfaces){
        try{
            //1 生成动态代理源代码
            String src = generateSrc(interfaces);
            //System.out.println(src);
            //2 将源代码写入磁盘
            File f = writeProxy(src);
            //3、把生成的.java文件编译成.class文件
            compilerProxy(f);
            f.delete();
        }catch (Exception e){
            e.printStackTrace();
        }

    }

    public static Object newProxyInstance(Class[] interfaces,MyInvocationHandler invocationHandler){
        try {
            generateProxy(interfaces);
            //4、编译生成的.class文件加载到JVM中来
            Class proxyClass =  Class.forName(MyProxy.class.getPackage().getName()+".$Proxy0");
            Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);

            //5、返回字节码重组以后的新的代理对象
            return c.newInstance(invocationHandler);
        }catch (Exception e){

        }
        return null;
    }

    private static void compilerProxy(File f)throws Exception {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager manage = compiler.getStandardFileManager(null,null,null);
        Iterable iterable = manage.getJavaFileObjects(f);

        JavaCompiler.CompilationTask task = compiler.getTask(null,manage,null,null,null,iterable);
        task.call();
        manage.close();
    }

    private static File writeProxy(String src)throws Exception {
        String path = MyProxy.class.getResource("").getPath();
        File f = new File(path + "$Proxy0.java");
        //System.out.println(f.getPath());
        FileWriter fw = new FileWriter(f);
        fw.write(src);
        fw.flush();
        fw.close();
        return f;
    }

    private static String generateSrc(Class[] interfaces) {
        StringBuilder sb = new StringBuilder();
        sb.append("package ");
        sb.append(MyProxy.class.getPackage().getName()).append(";").append(lin);
        for (Class anInterface : interfaces) {
            sb.append("import ").append(anInterface.getName()).append(";").append(lin);
        }
        sb.append("import java.lang.reflect.*;").append(lin);
        sb.append("public class $Proxy0 implements ");
        for (int i=0;i<interfaces.length;i++) {
            Class anInterface = interfaces[i];
            if(i<interfaces.length-1){
                sb.append(anInterface.getSimpleName()).append(",");
            }else {
                sb.append(anInterface.getSimpleName()).append(" {").append(lin);
            }
        }
        sb.append(tt).append("MyInvocationHandler hl;").append(lin);
        sb.append(tt).append("public $Proxy0(MyInvocationHandler h) { ").append(lin);
        sb.append(tt).append(tt).append("this.hl = h;").append(lin);
        sb.append(tt).append("}").append(lin);

        for (Class anInterface : interfaces) {
            for (Method method : anInterface.getMethods()) {
                Class<?>[] params = method.getParameterTypes();

                StringBuffer paramNames = new StringBuffer();
                StringBuffer paramValues = new StringBuffer();
                StringBuffer paramClasses = new StringBuffer();
                for (int i = 0; i < params.length; i++) {
                    Class clazz = params[i];
                    String type = clazz.getName();
                    String paramName = "arg"+i;
                    paramNames.append(type + " " +  paramName);
                    paramValues.append(paramName);
                    paramClasses.append(clazz.getName() + ".class");
                    if(i < params.length-1){
                        paramNames.append(", ");
                        paramClasses.append(", ");
                        paramValues.append(", ");
                    }
                }

                sb.append(tt).append("public " + method.getReturnType().getName() + " " + method.getName() + "(" + paramNames.toString() + ") {" + lin);
                sb.append(tt).append(tt).append("try{" + lin);
                sb.append(tt).append(tt).append(tt).append("Method m = " + anInterface.getSimpleName() + ".class.getMethod(\"" + method.getName() + "\",new Class[]{" + paramClasses.toString() + "});" + lin);
                sb.append(tt).append(tt).append(tt).append((hasReturnValue(method.getReturnType()) ? "return " : "") + getCaseCode("this.hl.invoke(this,m,new Object[]{" + paramValues + "})",method.getReturnType()) + ";" + lin);
                sb.append(tt).append(tt).append("}catch(Error _ex) { }").append(lin);
                sb.append(tt).append(tt).append("catch(Throwable e){" + lin);
                sb.append(tt).append(tt).append(tt).append("throw new UndeclaredThrowableException(e);" + lin);
                sb.append(tt).append(tt).append("}").append(lin);
                sb.append(getReturnEmptyCode(method.getReturnType()));
                sb.append(tt).append("}").append(lin);


            }
        }
        sb.append("}").append(lin);


        return sb.toString();
    }

    private static String getReturnEmptyCode(Class<?> returnClass){
        if(mappings.containsKey(returnClass)){
            return "return 0;";
        }else if(returnClass == void.class){
            return "";
        }else {
            return "return null;";
        }
    }

    private static Map<Class,Class> mappings = new HashMap<Class, Class>();
    static {
        mappings.put(int.class,Integer.class);
    }
    private static String getCaseCode(String code,Class<?> returnClass){
        if(mappings.containsKey(returnClass)){
            return "((" + mappings.get(returnClass).getName() +  ")" + code + ")." + returnClass.getSimpleName() + "Value()";
        }
        return code;
    }

    private static boolean hasReturnValue(Class<?> clazz){
        return clazz != void.class;
    }

}
